diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index bfcc860..d70944b 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to the KoalaSync browser extension and relay server. --- +## [v2.2.2] — 2026-06-09 + +### Added +- **Chrome Web Store i18n Support**: Added `default_locale: "en"` to manifest and created `_locales/*/messages.json` for all 13 supported languages. This unlocks the language selection dropdown in the Chrome Web Store dashboard, allowing translated store listings (title, description) per locale. The extension's own UI translations (`locales/*.json` + `i18n.js`) remain unchanged. +- **Locale test coverage**: Extended `scripts/test-locales.js` to validate all `_locales/*/messages.json` files (correct format, required keys, no duplicates) and verify `default_locale` is set in the manifest. + +### Fixed +- **Copy Logs button alignment**: Removed stray `margin-top: 8px` inherited from `.secondary` class that pushed the button 8px down in the connection status row. + +--- + ## [v2.2.1] — 2026-06-09 ### Added diff --git a/extension/_locales/de/messages.json b/extension/_locales/de/messages.json new file mode 100644 index 0000000..8b84354 --- /dev/null +++ b/extension/_locales/de/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Synchronisiere die Videowiedergabe auf YouTube, Netflix, Emby, Jellyfin und jeder HTML5-Seite in Echtzeit mit Freunden." + } +} diff --git a/extension/_locales/en/messages.json b/extension/_locales/en/messages.json new file mode 100644 index 0000000..3132906 --- /dev/null +++ b/extension/_locales/en/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends." + } +} diff --git a/extension/_locales/es/messages.json b/extension/_locales/es/messages.json new file mode 100644 index 0000000..9ed8d3b --- /dev/null +++ b/extension/_locales/es/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Sincroniza la reproducción de video en YouTube, Netflix, Emby, Jellyfin y cualquier sitio HTML5 en tiempo real con amigos." + } +} diff --git a/extension/_locales/fr/messages.json b/extension/_locales/fr/messages.json new file mode 100644 index 0000000..37cbe71 --- /dev/null +++ b/extension/_locales/fr/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Synchronisez la lecture vidéo sur YouTube, Netflix, Emby, Jellyfin et tout site HTML5 en temps réel avec vos amis." + } +} diff --git a/extension/_locales/it/messages.json b/extension/_locales/it/messages.json new file mode 100644 index 0000000..118ff90 --- /dev/null +++ b/extension/_locales/it/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Sincronizza la riproduzione video su YouTube, Netflix, Emby, Jellyfin e qualsiasi sito HTML5 in tempo reale con gli amici." + } +} diff --git a/extension/_locales/ja/messages.json b/extension/_locales/ja/messages.json new file mode 100644 index 0000000..5833c2f --- /dev/null +++ b/extension/_locales/ja/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "YouTube、Netflix、Emby、Jellyfin、その他HTML5サイトでの動画再生を友達とリアルタイムで同期します。" + } +} diff --git a/extension/_locales/ko/messages.json b/extension/_locales/ko/messages.json new file mode 100644 index 0000000..12f71e4 --- /dev/null +++ b/extension/_locales/ko/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "YouTube, Netflix, Emby, Jellyfin 및 모든 HTML5 사이트에서 친구들과 실시간으로 비디오 재생을 동기화하세요." + } +} diff --git a/extension/_locales/nl/messages.json b/extension/_locales/nl/messages.json new file mode 100644 index 0000000..ab97481 --- /dev/null +++ b/extension/_locales/nl/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Synchroniseer video-afspelen op YouTube, Netflix, Emby, Jellyfin en elke HTML5-site in realtime met vrienden." + } +} diff --git a/extension/_locales/pl/messages.json b/extension/_locales/pl/messages.json new file mode 100644 index 0000000..e13b068 --- /dev/null +++ b/extension/_locales/pl/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Synchronizuj odtwarzanie wideo na YouTube, Netflix, Emby, Jellyfin i dowolnej stronie HTML5 w czasie rzeczywistym ze znajomymi." + } +} diff --git a/extension/_locales/pt_BR/messages.json b/extension/_locales/pt_BR/messages.json new file mode 100644 index 0000000..bb6a136 --- /dev/null +++ b/extension/_locales/pt_BR/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Sincronize a reprodução de vídeo no YouTube, Netflix, Emby, Jellyfin e qualquer site HTML5 em tempo real com amigos." + } +} diff --git a/extension/_locales/pt_PT/messages.json b/extension/_locales/pt_PT/messages.json new file mode 100644 index 0000000..bb6a136 --- /dev/null +++ b/extension/_locales/pt_PT/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Sincronize a reprodução de vídeo no YouTube, Netflix, Emby, Jellyfin e qualquer site HTML5 em tempo real com amigos." + } +} diff --git a/extension/_locales/ru/messages.json b/extension/_locales/ru/messages.json new file mode 100644 index 0000000..ff73697 --- /dev/null +++ b/extension/_locales/ru/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "Синхронизируйте воспроизведение видео на YouTube, Netflix, Emby, Jellyfin и любых HTML5-сайтах в реальном времени с друзьями." + } +} diff --git a/extension/_locales/tr/messages.json b/extension/_locales/tr/messages.json new file mode 100644 index 0000000..7641824 --- /dev/null +++ b/extension/_locales/tr/messages.json @@ -0,0 +1,8 @@ +{ + "appName": { + "message": "KoalaSync" + }, + "appDesc": { + "message": "YouTube, Netflix, Emby, Jellyfin ve herhangi bir HTML5 sitesinde video oynatmayı arkadaşlarınızla gerçek zamanlı olarak senkronize edin." + } +} diff --git a/extension/manifest.base.json b/extension/manifest.base.json index 468ec74..366fcb3 100644 --- a/extension/manifest.base.json +++ b/extension/manifest.base.json @@ -1,5 +1,6 @@ { "manifest_version": 3, + "default_locale": "en", "name": "KoalaSync", "version": "2.2.1", "description": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends.", diff --git a/extension/popup.html b/extension/popup.html index a7c78fa..44f967f 100644 --- a/extension/popup.html +++ b/extension/popup.html @@ -600,7 +600,7 @@ Disconnected - + diff --git a/scripts/test-locales.js b/scripts/test-locales.js index e45069c..81bac1a 100644 --- a/scripts/test-locales.js +++ b/scripts/test-locales.js @@ -98,11 +98,128 @@ for (const file of localeFiles) { } } +// ────────────────────────────────────── +// Verify Chrome _locales/*/messages.json +// ────────────────────────────────────── +console.log('\nVerifying Chrome _locales/messages.json structure...\n'); + +const chromeLocalesDir = path.join(__dirname, '..', 'extension', '_locales'); + +// Map custom locale codes to Chrome's underscore format +const chromeLocaleMap = { + 'en': 'en', 'de': 'de', 'fr': 'fr', 'es': 'es', 'it': 'it', + 'ja': 'ja', 'ko': 'ko', 'nl': 'nl', 'pl': 'pl', + 'pt-BR': 'pt_BR', 'pt': 'pt_PT', 'ru': 'ru', 'tr': 'tr' +}; + +// Read SUPPORTED_LANGUAGES from i18n.js +const i18nContent = fs.readFileSync(i18nPath, 'utf8'); +const langMatch = i18nContent.match(/export const SUPPORTED_LANGUAGES = \[(.*?)\];/); +const supportedLangs = langMatch + ? langMatch[1].split(',').map(s => s.trim().replace(/['"]/g, '')) + : []; + +// Verify default_locale in manifest.base.json +const manifestPath = path.join(__dirname, '..', 'extension', 'manifest.base.json'); +try { + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + if (!manifest.default_locale) { + hasError = true; + console.error('❌ manifest.base.json is missing "default_locale"'); + } else if (manifest.default_locale !== 'en') { + hasError = true; + console.error(`❌ manifest.base.json default_locale is "${manifest.default_locale}", expected "en"`); + } else { + console.log('✓ manifest.base.json has default_locale: "en"'); + } +} catch (err) { + hasError = true; + console.error('❌ Failed to read manifest.base.json:', err.message); +} + +// Verify _locales structure for each supported language +const expectedKeys = ['appName', 'appDesc']; +for (const lang of supportedLangs) { + const chromeLocale = chromeLocaleMap[lang]; + if (!chromeLocale) { + hasError = true; + console.error(`❌ No Chrome locale mapping for "${lang}" in chromeLocaleMap`); + continue; + } + + const msgPath = path.join(chromeLocalesDir, chromeLocale, 'messages.json'); + + if (!fs.existsSync(msgPath)) { + hasError = true; + console.error(`❌ Missing _locales/${chromeLocale}/messages.json for language "${lang}"`); + continue; + } + + try { + const raw = fs.readFileSync(msgPath, 'utf8'); + const messages = JSON.parse(raw); + const keys = Object.keys(messages); + + // Detect duplicate top-level keys via regex (JSON.parse silently keeps last value) + const topKeyRe = /^\s{2}"(\w+)"\s*:/gm; + const topSeen = {}; + let topDupes = []; + let tm; + while ((tm = topKeyRe.exec(raw)) !== null) { + if (topSeen[tm[1]]) topDupes.push(tm[1]); + topSeen[tm[1]] = true; + } + if (topDupes.length > 0) { + hasError = true; + console.error(`❌ _locales/${chromeLocale}/messages.json has duplicate keys: ${[...new Set(topDupes)].join(', ')}`); + continue; + } + + // Validate each entry has a "message" field + for (const key of keys) { + if (!messages[key].message || typeof messages[key].message !== 'string') { + hasError = true; + console.error(`❌ _locales/${chromeLocale}/messages.json: "${key}" is missing a valid "message" field`); + } + } + + // Check for missing or extra keys vs expected baseline + const missing = expectedKeys.filter(k => !keys.includes(k)); + const extra = keys.filter(k => !expectedKeys.includes(k)); + if (missing.length > 0) { + hasError = true; + console.error(`❌ _locales/${chromeLocale}/messages.json missing keys: ${missing.join(', ')}`); + } + if (extra.length > 0) { + console.log(`ℹ️ _locales/${chromeLocale}/messages.json has extra keys (ok): ${extra.join(', ')}`); + } + if (missing.length === 0) { + console.log(`✓ _locales/${chromeLocale}/messages.json is valid and complete`); + } + } catch (err) { + hasError = true; + console.error(`❌ Failed to parse _locales/${chromeLocale}/messages.json:`, err.message); + } +} + +// Detect orphan _locales directories (no matching supported language) +if (fs.existsSync(chromeLocalesDir)) { + const chromeCodes = supportedLangs.map(l => chromeLocaleMap[l]).filter(Boolean); + const dirs = fs.readdirSync(chromeLocalesDir); + for (const dir of dirs) { + const dirPath = path.join(chromeLocalesDir, dir); + if (fs.statSync(dirPath).isDirectory() && dir !== '.git' && !chromeCodes.includes(dir)) { + hasError = true; + console.error(`❌ Orphan _locales/${dir}/ directory exists but no matching language in SUPPORTED_LANGUAGES`); + } + } +} + console.log(''); if (hasError) { console.error('❌ Locale consistency check failed! Please fix the errors listed above.'); process.exit(1); } else { - console.log('🎉 All locale files are perfectly synchronized and consistent!'); + console.log('🎉 All locale files (locales/ and _locales/) are valid and consistent!'); process.exit(0); }