mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-26 12:08:15 +00:00
feat: add Chrome _locales for store i18n, fix copy logs alignment, extend locale test
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"appName": {
|
||||
"message": "KoalaSync"
|
||||
},
|
||||
"appDesc": {
|
||||
"message": "YouTube、Netflix、Emby、Jellyfin、その他HTML5サイトでの動画再生を友達とリアルタイムで同期します。"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"appName": {
|
||||
"message": "KoalaSync"
|
||||
},
|
||||
"appDesc": {
|
||||
"message": "YouTube, Netflix, Emby, Jellyfin 및 모든 HTML5 사이트에서 친구들과 실시간으로 비디오 재생을 동기화하세요."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"appName": {
|
||||
"message": "KoalaSync"
|
||||
},
|
||||
"appDesc": {
|
||||
"message": "Синхронизируйте воспроизведение видео на YouTube, Netflix, Emby, Jellyfin и любых HTML5-сайтах в реальном времени с друзьями."
|
||||
}
|
||||
}
|
||||
@@ -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,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.",
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user