feat: add Chrome _locales for store i18n, fix copy logs alignment, extend locale test

This commit is contained in:
KoalaDev
2026-06-09 19:14:30 +02:00
parent cfbb070c57
commit 0f4ad39fb4
17 changed files with 235 additions and 2 deletions
+11
View File
@@ -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
+8
View File
@@ -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."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "YouTube、Netflix、Emby、Jellyfin、その他HTML5サイトでの動画再生を友達とリアルタイムで同期します。"
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "YouTube, Netflix, Emby, Jellyfin 및 모든 HTML5 사이트에서 친구들과 실시간으로 비디오 재생을 동기화하세요."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -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."
}
}
+8
View File
@@ -0,0 +1,8 @@
{
"appName": {
"message": "KoalaSync"
},
"appDesc": {
"message": "Синхронизируйте воспроизведение видео на YouTube, Netflix, Emby, Jellyfin и любых HTML5-сайтах в реальном времени с друзьями."
}
}
+8
View File
@@ -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."
}
}
+1
View File
@@ -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.",
+1 -1
View File
@@ -600,7 +600,7 @@
<span id="connText" style="flex:1;">Disconnected</span>
<span id="connPing" style="font-size:11px; font-family:monospace; font-weight:600; opacity:0.8;"></span>
<button id="retryBtn" class="secondary" style="display:none; width: auto; padding: 4px 8px; font-size: 10px; margin: 0;" title="Attempt to reconnect to the server" data-i18n="BTN_RETRY" data-i18n-title="BTN_RETRY_TOOLTIP">RETRY</button>
<button id="copyLogs" class="btn secondary" style="width: auto; padding: 4px 10px; font-size: 11px;" title="Copy logs to clipboard for sharing" data-i18n="BTN_COPY_LOGS" data-i18n-title="BTN_COPY_LOGS_TOOLTIP">Copy Logs</button>
<button id="copyLogs" class="btn secondary" style="width: auto; padding: 4px 10px; font-size: 11px; margin: 0;" title="Copy logs to clipboard for sharing" data-i18n="BTN_COPY_LOGS" data-i18n-title="BTN_COPY_LOGS_TOOLTIP">Copy Logs</button>
</div>
<label title="Technical details about the currently selected video element" data-i18n="LABEL_VIDEO_DEBUG" data-i18n-title="LABEL_VIDEO_DEBUG_TOOLTIP">Video Debug Info</label>
+118 -1
View File
@@ -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);
}