mirror of
https://github.com/Shik3i/KoalaSync.git
synced 2026-07-27 04:20:25 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d76e9195c4 | |||
| 6652a06840 | |||
| b1a89cff41 | |||
| e4a77a3ef4 | |||
| 5adcce2074 | |||
| a1398ed0e4 |
+27
-15
@@ -12,7 +12,7 @@ Please note that by participating in this project, you agree to abide by our [Co
|
||||
|------|-------------|
|
||||
| **Bug Reports** | Found a bug? Open an issue with repro steps (see template below). |
|
||||
| **Code** | Fix bugs, add features, or improve the extension / server / website. |
|
||||
| **Translations** | Help localize the extension and website into more languages. See [TRANSLATION.md](website/TRANSLATION.md). |
|
||||
| **Translations** | Help localize the extension and website into more languages. See [TRANSLATION.md](docs/TRANSLATION.md). |
|
||||
| **Documentation** | Improve docs, fix typos, or add missing examples. |
|
||||
| **Security** | Found a vulnerability? See [SECURITY.md](SECURITY.md) — do NOT open a public issue. |
|
||||
|
||||
@@ -100,15 +100,23 @@ KoalaSync uses a **single source of truth** for all protocol constants in `share
|
||||
|
||||
---
|
||||
|
||||
## Pull Request Process
|
||||
## Open Source Workflow & Pull Requests
|
||||
|
||||
1. **Branch** from `main` for your feature or fix.
|
||||
2. **Test locally** on both Chrome and Firefox.
|
||||
3. **Website changes**: Run `node website/build.js` and verify the compiled output in `www/`.
|
||||
4. **Lint**: Ensure `npm run lint` passes with zero errors and zero warnings.
|
||||
5. **Syntax**: Run `node -c` on every modified `.js` file.
|
||||
6. **Protocol changes**: Update relevant documentation in `docs/`.
|
||||
7. **Submit your PR** with a clear description and linked issue (if applicable).
|
||||
If you are new to open-source contributions, follow these steps to propose your changes:
|
||||
|
||||
1. **Fork the Repository**: Click the "Fork" button at the top right of this repository to create your own copy of KoalaSync.
|
||||
2. **Clone your Fork**: `git clone https://github.com/YOUR-USERNAME/KoalaSync.git`
|
||||
3. **Create a Branch**: `git checkout -b my-new-feature` (e.g. `feature/dark-mode` or `fix/translation-de`)
|
||||
4. **Make your Changes**: Edit the files, then verify them locally.
|
||||
- *Extension/Server changes*: Test on Chrome/Firefox and check `npm run lint`.
|
||||
- *Website/Translation changes*: Run `node website/build.js` and check the output in `www/`.
|
||||
5. **Commit and Push**: `git commit -m "Add my feature"` and `git push origin my-new-feature`
|
||||
6. **Open a Pull Request (PR)**: Go to the original KoalaSync repository on GitHub and click "New Pull Request".
|
||||
|
||||
### PR Code Requirements
|
||||
- **Lint**: Ensure `npm run lint` passes with zero errors and warnings.
|
||||
- **Syntax**: Run `node -c` on every modified `.js` file.
|
||||
- **Protocol changes**: Update relevant documentation in `docs/`.
|
||||
|
||||
---
|
||||
|
||||
@@ -140,14 +148,18 @@ If you cannot access the Status tab, include as much of the following manually:
|
||||
|
||||
---
|
||||
|
||||
## Translation Contributions
|
||||
## Translation Contributions (Translators Welcome!)
|
||||
|
||||
KoalaSync supports 6 languages: English, German, French, Spanish, Portuguese (Brazilian), and Russian.
|
||||
We welcome native speakers to help translate KoalaSync! You **do not** need deep programming knowledge to contribute translations.
|
||||
|
||||
To add or improve translations:
|
||||
1. Edit the locale files in `website/locales/` (for the website).
|
||||
2. For extension translations, see [TRANSLATION.md](website/TRANSLATION.md).
|
||||
3. Run `node website/build.js` to regenerate the static site.
|
||||
KoalaSync supports multiple languages. To add or improve translations:
|
||||
1. Read the **[Translation Guide](docs/TRANSLATION.md)** first. It explains how our localization system works.
|
||||
2. Edit the `.json` files in `website/locales/` (for the website) and `extension/locales/` (for the extension).
|
||||
3. Test your translations locally by running:
|
||||
- `node scripts/test-locales.js` (for extension)
|
||||
- `node scripts/test-website-locales.mjs` (for website)
|
||||
- `node website/build.js` (to build the site)
|
||||
4. Follow the **Open Source Workflow** above (Fork -> Branch -> Edit -> PR) to submit your translations.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
<p align="center"><i>KoalaSync is a lightweight Browser Extension and Relay Server for synchronized video playback on almost any website with a video element—YouTube, Twitch, Netflix, Emby, Jellyfin, and beyond. Built with a focus on <b>Data Sovereignty</b> and <b>Performance</b>.</i></p>
|
||||
|
||||
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.2.4 Release!</b> — See what's changed</a></p>
|
||||
<p align="center"><a href="docs/CHANGELOG.md"><b>New v2.3.0 Release!</b> — See what's changed</a></p>
|
||||
|
||||
### 🌟 Why KoalaSync?
|
||||
|
||||
@@ -61,7 +61,7 @@ The easiest and safest way to install KoalaSync is directly through the official
|
||||
Both the official KoalaSync website and the **v2.0 Browser Extension** feature full dynamic localization:
|
||||
- **Available Languages**: Support is included for 13 languages: English (`en`), German (`de`), French (`fr`), Spanish (`es`), Portuguese (Brazil) (`pt-BR`), Russian (`ru`), Italian (`it`), Polish (`pl`), Turkish (`tr`), Dutch (`nl`), Japanese (`ja`), Korean (`ko`), and European Portuguese (`pt`).
|
||||
- **Real-Time Extension Localization**: Inside the extension Settings panel, users can swap languages instantly. The entire interface, notifications, Empty States, and onboarding guides re-translate dynamically in real-time.
|
||||
- **Contributing**: We welcome community translations for both the website and the extension! Please refer directly to the [TRANSLATION.md](website/TRANSLATION.md) guide for step-by-step instructions on how to audit, refine, or add new languages.
|
||||
- **Contributing**: We welcome community translations for both the website and the extension! Please refer directly to the [TRANSLATION.md](docs/TRANSLATION.md) guide for step-by-step instructions on how to audit, refine, or add new languages.
|
||||
|
||||
|
||||
---
|
||||
@@ -103,7 +103,7 @@ To connect your extension to a self-hosted server, open the popup → **Room** t
|
||||
|
||||
To verify your relay is reachable from outside, visit `https://your-domain.com` in a browser — it should return `{"status":"online","service":"KoalaSync Relay"}`.
|
||||
|
||||
#### Supply Chain Security (v2.2.4+)
|
||||
#### Supply Chain Security (v2.3.0+)
|
||||
|
||||
All official release artifacts (Docker images and extension binaries) are published with signed [artifact attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations) to prove they were built from this repository's source code.
|
||||
|
||||
@@ -125,7 +125,7 @@ gh attestation verify dist/koalasync-chrome.zip \
|
||||
|
||||
- **[CHANGELOG.md](docs/CHANGELOG.md)**: Full version history for the extension and relay server.
|
||||
- **[TESTED_SERVICES.md](docs/TESTED_SERVICES.md)**: Detailed compatibility matrix of tested streaming platforms and known limitations.
|
||||
- **[TRANSLATION.md](website/TRANSLATION.md)**: Translation and localization guide for contributors.
|
||||
- **[TRANSLATION.md](docs/TRANSLATION.md)**: Translation and localization guide for contributors.
|
||||
- **[PRIVACY.md](docs/PRIVACY.md)**: Data Handling and Privacy Policy.
|
||||
- **[CONTRIBUTING.md](CONTRIBUTING.md)**: How to help make KoalaSync better.
|
||||
- **[CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)**: Our community standards and expectations.
|
||||
|
||||
+12
-1
@@ -4,7 +4,18 @@ All notable changes to the KoalaSync browser extension and relay server.
|
||||
|
||||
---
|
||||
|
||||
## [v2.3.0] — Unreleased
|
||||
## [v2.3.1] — 2026-06-15
|
||||
|
||||
### Fixed
|
||||
- **Server: Race condition on concurrent peer joins**: Added a per-peerId serialization lock (`peerJoinLocks`) that prevents two connections with the same `peerId` from both passing the deduplication check and simultaneously registering in `peerToSocket`. Previously, rapid reconnects could leave stale mappings that caused cross-room ACK/PING misdelivery.
|
||||
- **Server: Crash-safe error handling in teardown paths**: Wrapped `removePeerFromRoom` calls in the disconnect, leave, and reaper handlers with try/catch to prevent an unhandled exception in any teardown path from crashing the process.
|
||||
- **Server: Smart unhandled rejection handling**: Replaced the immediate `process.exit(1)` on any `unhandledRejection` with a rate-limited approach — the server now exits only after 5 unhandled rejections within 60 seconds, surviving transient errors while still failing fast on cascading crashes.
|
||||
- **Server: Reduced GC pressure in admin health metrics**: Replaced the three-pass `Array.from()` / `map()` / `reduce()` / `filter()` pattern in `buildHealthPayload()` with a single `for-of` loop, eliminating temporary array allocations proportional to the number of active rooms.
|
||||
- **Server: Test isolation for rate-limit denial counters**: `stopServerForTests()` now resets the `rateLimitDenied` counters between test runs.
|
||||
|
||||
---
|
||||
|
||||
## [v2.3.0] — 2026-06-14
|
||||
|
||||
### Added
|
||||
- **Extension: New Interactive Onboarding Tour**: A fully redesigned, interactive step-by-step onboarding experience.
|
||||
|
||||
@@ -5,3 +5,4 @@ This directory contains deep-dives into the KoalaSync protocol and architecture.
|
||||
- [HOW_IT_WORKS.md](HOW_IT_WORKS.md): Step-by-step walkthrough of every user flow, from room creation to synchronized playback. Ideal for store reviewers and manual testers.
|
||||
- [ARCHITECTURE.md](ARCHITECTURE.md): Communication flows, Dual Heartbeat, and Sync logic.
|
||||
- [SYNC_GUIDE.md](SYNC_GUIDE.md): Protocol constants and sync requirements.
|
||||
- [TRANSLATION.md](TRANSLATION.md): Translation and localization guide for the extension and website.
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
# KoalaSync Translation & Localization Guide
|
||||
|
||||
Welcome to the **KoalaSync** translation guide! We rely on the open-source community to make KoalaSync accessible to users worldwide.
|
||||
|
||||
KoalaSync is split into two independent translation areas. You can translate either one, or both:
|
||||
1. **The Browser Extension** (`extension/locales/`): The core product that users interact with daily.
|
||||
2. **The Website** (`website/locales/`): The landing page and invitation bridge.
|
||||
|
||||
---
|
||||
|
||||
## 📊 Supported Languages Dashboard
|
||||
|
||||
We divide supported languages into two tiers: **Core Languages** (fully hand-crafted and audited by native speakers) and **Extended Languages** (auto-generated using translation models to expand initial coverage).
|
||||
|
||||
> [!TIP]
|
||||
> **Help Us Improve!**
|
||||
> We welcome community contributions to audit "Auto-Generated" translations and elevate them to "Verified" status.
|
||||
|
||||
| Language Code | Language Name | Verification Status | Rationale / Context |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `en` | 🇬🇧 **English** | `100% Manually Verified` | Global default language (Verified by developer) |
|
||||
| `de` | 🇩🇪 **German** | `100% Manually Verified` | Developer's native language |
|
||||
| `fr` | 🇫🇷 **French** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `es` | 🇪🇸 **Spanish** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `pt-BR` | 🇧🇷 **Portuguese (Brasil)** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `ru` | 🇷🇺 **Russian** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `it` | 🇮🇹 **Italian** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `pl` | 🇵🇱 **Polish** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `tr` | 🇹🇷 **Turkish** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `nl` | 🇳🇱 **Dutch** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `ja` | 🇯🇵 **Japanese** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `ko` | 🇰🇷 **Korean** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `pt` | 🇵🇹 **European Portuguese** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
|
||||
> [!WARNING]
|
||||
> **Autogeneration Quality Rule**
|
||||
> Any newly contributed languages must be marked as `"Auto-Generated"` in this table until fully reviewed and signed off by a native speaker in a pull request.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ How to Translate KoalaSync
|
||||
|
||||
Here is the exact step-by-step process for contributing translations.
|
||||
|
||||
### Step 1: Fork and Clone the Repository
|
||||
If you are an external contributor, start with the standard Open Source workflow:
|
||||
1. Click the "Fork" button on GitHub to create your own copy of the repository.
|
||||
2. Clone your fork locally: `git clone https://github.com/YOUR-USERNAME/KoalaSync.git`
|
||||
3. Create a branch: `git checkout -b translation/my-language`
|
||||
|
||||
### Step 2: Translate the Extension
|
||||
The browser extension handles real-time syncing, settings, and popups.
|
||||
1. Navigate to [`extension/locales/`](file:///Users/koala/Documents/Workspaces/KoalaSync/extension/locales/).
|
||||
2. Edit an existing `[lang].json` or copy `en.json` to create a new one (e.g., `it.json`).
|
||||
3. Translate all the string values. **Do not change the JSON keys.**
|
||||
|
||||
### Step 3: Translate the Website
|
||||
The website hosts the landing page and invitation bridge.
|
||||
1. Navigate to [`website/locales/`](file:///Users/koala/Documents/Workspaces/KoalaSync/website/locales/).
|
||||
2. Edit an existing `[lang].json` or copy `en.json` to create a new one.
|
||||
3. Translate all the string values. **Do not change the JSON keys.**
|
||||
4. If creating a **brand new language**, configure the metadata at the top of your JSON file:
|
||||
```json
|
||||
{
|
||||
"LANG_CODE": "it",
|
||||
"HTML_CLASS": "lang-it",
|
||||
"CANONICAL_PATH": "it/",
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN"
|
||||
}
|
||||
```
|
||||
5. If creating a **brand new language**, register it in `website/build.js` by adding it to the `languages` array.
|
||||
|
||||
### Step 4: Verify Locally
|
||||
Ensure your JSON files are valid and all keys match the English baseline. Open your terminal in the KoalaSync root folder and run:
|
||||
```bash
|
||||
# Tests the extension locales for missing keys or syntax errors
|
||||
node scripts/test-locales.js
|
||||
|
||||
# Tests the website locales for missing keys or syntax errors
|
||||
node scripts/test-website-locales.mjs
|
||||
|
||||
# Builds the website with your new translations
|
||||
node website/build.js
|
||||
```
|
||||
*Note: If you receive any errors about missing keys or `TODO` placeholders, please fix them before submitting.*
|
||||
|
||||
### Step 5: Commit and Pull Request
|
||||
1. Open this `TRANSLATION.md` file and add/update your language in the **Supported Languages Dashboard** above. Mark it as `100% Manually Verified` if you are a native speaker.
|
||||
2. Commit your changes: `git commit -m "Update Italian translations"`
|
||||
3. Push to your fork: `git push origin translation/my-language`
|
||||
4. Open a **Pull Request** on the main KoalaSync repository on GitHub.
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ Strict Legal Exclusion Rule
|
||||
|
||||
Our legal pages have strict constraints to protect user privacy and avoid regulatory liabilities.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **DO NOT TRANSLATE LEGAL DOCUMENTS**
|
||||
> The legal notice ([impressum.html](file:///Users/koala/Documents/Workspaces/KoalaSync/website/impressum.html)) and privacy policy ([datenschutz.html](file:///Users/koala/Documents/Workspaces/KoalaSync/website/datenschutz.html)) **MUST remain exclusively in English and German**.
|
||||
>
|
||||
> * **Rationale:** Legal compliance under the European Union General Data Protection Regulation (GDPR) and the German Digital Services Act (DDG). Offering automated translations of legally binding notices introduces compliance risks due to potential mistranslations of liability limits.
|
||||
> * **Technical Fallback:** Our system automatically falls back to **English** for legal pages if a user visits them in an unsupported language, so you do not need to worry about this.
|
||||
@@ -1,204 +0,0 @@
|
||||
# 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: `<button class="tab-btn" data-tab="tab-settings" data-i18n="TAB_SETTINGS" data-i18n-title="TAB_SETTINGS_TOOLTIP">Settings</button>`
|
||||
* Add Language Dropdown in the Settings Panel:
|
||||
```html
|
||||
<div class="form-group" style="display: flex; align-items: center; justify-content: space-between; background: var(--card); padding: 10px; border-radius: 8px; margin-bottom: 12px; border: 1px solid #334155;">
|
||||
<label style="margin-bottom: 0;" data-i18n="LABEL_LANGUAGE" title="Choose your preferred extension language">App Language</label>
|
||||
<select id="langSelector" style="width: 150px; padding: 6px 10px; font-size: 13px; cursor: pointer;">
|
||||
<option value="en">English</option>
|
||||
<option value="de">Deutsch</option>
|
||||
<option value="fr">Français</option>
|
||||
<option value="es">Español</option>
|
||||
<option value="pt-BR">Português (Brasil)</option>
|
||||
<option value="ru">Русский</option>
|
||||
</select>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 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.
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 3,
|
||||
"default_locale": "en",
|
||||
"name": "KoalaSync",
|
||||
"version": "2.2.4",
|
||||
"version": "2.3.0",
|
||||
"description": "Synchronize video playback on YouTube, Netflix, Emby, Jellyfin, and any HTML5 site in real-time with friends.",
|
||||
"permissions": [
|
||||
"storage",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "koalasync",
|
||||
"version": "2.2.4",
|
||||
"version": "2.3.0",
|
||||
"description": "KoalaSync Build Scripts",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
+52
-14
@@ -115,6 +115,7 @@ export const rooms = new Map();
|
||||
const socketToRoom = new Map();
|
||||
const peerToSocket = new Map(); // peerId -> socketId (Global lookup)
|
||||
const roomCreationLocks = new Map(); // roomId -> Promise (prevents race on room creation)
|
||||
const peerJoinLocks = new Map(); // peerId -> Promise (prevents race on same peerId joins)
|
||||
|
||||
function log(type, message, details = '') {
|
||||
const debugLogging = process.env.DEBUG_LOGGING === '1';
|
||||
@@ -469,7 +470,20 @@ io.on('connection', (socket) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!createdByMe) {
|
||||
let peerLockPromise = peerJoinLocks.get(peerId);
|
||||
if (peerLockPromise) {
|
||||
await peerLockPromise;
|
||||
room = rooms.get(roomId);
|
||||
if (!room) {
|
||||
socket.emit(EVENTS.ERROR, { message: "Room no longer exists" });
|
||||
return;
|
||||
}
|
||||
}
|
||||
let resolvePeerLock;
|
||||
peerLockPromise = new Promise(resolve => { resolvePeerLock = resolve; });
|
||||
peerJoinLocks.set(peerId, peerLockPromise);
|
||||
try {
|
||||
if (!createdByMe) {
|
||||
if (room.passwordHash) {
|
||||
if (!password || hashPassword(password) !== room.passwordHash) {
|
||||
recordAuthFailure(ip, roomId);
|
||||
@@ -527,6 +541,10 @@ io.on('connection', (socket) => {
|
||||
activeLobby: room.activeLobby || null
|
||||
});
|
||||
log('ROOM', `Peer ${peerId} joined: ${roomId.substring(0, 3)}***`);
|
||||
} finally {
|
||||
peerJoinLocks.delete(peerId);
|
||||
resolvePeerLock();
|
||||
}
|
||||
} catch (err) {
|
||||
log('ERROR', `Join error for ${socket.id}`, err);
|
||||
if (socket.connected) {
|
||||
@@ -644,10 +662,14 @@ io.on('connection', (socket) => {
|
||||
});
|
||||
|
||||
socket.on(EVENTS.LEAVE_ROOM, () => {
|
||||
const mapping = socketToRoom.get(socket.id);
|
||||
if (mapping) {
|
||||
socket.leave(mapping.roomId);
|
||||
removePeerFromRoom(socket.id, mapping.roomId, 'leave');
|
||||
try {
|
||||
const mapping = socketToRoom.get(socket.id);
|
||||
if (mapping) {
|
||||
socket.leave(mapping.roomId);
|
||||
removePeerFromRoom(socket.id, mapping.roomId, 'leave');
|
||||
}
|
||||
} catch (err) {
|
||||
log('ERROR', 'removePeerFromRoom failed in leave', err);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -723,10 +745,11 @@ io.on('connection', (socket) => {
|
||||
roomListCooldowns.delete(socket.id);
|
||||
const mapping = socketToRoom.get(socket.id);
|
||||
if (mapping) {
|
||||
// Socket is already disconnected — no need to call socket.leave().
|
||||
// removePeerFromRoom uses io.to() for notifications, which correctly
|
||||
// excludes this dead socket since it has already left all rooms.
|
||||
removePeerFromRoom(socket.id, mapping.roomId, 'disconnect');
|
||||
try {
|
||||
removePeerFromRoom(socket.id, mapping.roomId, 'disconnect');
|
||||
} catch (err) {
|
||||
log('ERROR', 'removePeerFromRoom failed in disconnect', err);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -751,12 +774,14 @@ const roomCleanupInterval = setInterval(() => {
|
||||
}
|
||||
}
|
||||
for (const sid of staleSids) {
|
||||
// Gracefully evict the socket from the Socket.IO room if it is
|
||||
// still technically connected (zombie with no heartbeat).
|
||||
const deadSocket = io.sockets?.sockets?.get(sid);
|
||||
if (deadSocket) deadSocket.leave(roomId);
|
||||
log('CLEANUP', `Pruning dead peer from room ${roomId.substring(0, 3)}***`);
|
||||
removePeerFromRoom(sid, roomId, 'reaper');
|
||||
try {
|
||||
removePeerFromRoom(sid, roomId, 'reaper');
|
||||
} catch (err) {
|
||||
log('ERROR', 'removePeerFromRoom failed in reaper', err);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Prune empty or inactive rooms
|
||||
@@ -818,6 +843,7 @@ export async function stopServerForTests() {
|
||||
socketToRoom.clear();
|
||||
peerToSocket.clear();
|
||||
roomCreationLocks.clear();
|
||||
peerJoinLocks.clear();
|
||||
connectionCounts.clear();
|
||||
failedAuthAttempts.clear();
|
||||
eventCounts.clear();
|
||||
@@ -827,6 +853,7 @@ export async function stopServerForTests() {
|
||||
healthResponseCache.clear();
|
||||
io.removeAllListeners();
|
||||
io.disconnectSockets(true);
|
||||
Object.assign(rateLimitDenied, { connections: 0, events: 0, health: 0, adminMetricsAuth: 0, roomList: 0 });
|
||||
if (!httpServer.listening) return;
|
||||
await new Promise((resolve, reject) => {
|
||||
httpServer.close((err) => err ? reject(err) : resolve());
|
||||
@@ -846,8 +873,19 @@ if (isMainModule) {
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
let unhandledRejectionCount = 0;
|
||||
let unhandledRejectionReset = Date.now();
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
log('ERROR', `Unhandled rejection: ${reason}`);
|
||||
process.exit(1);
|
||||
log('ERROR', `Unhandled rejection: ${reason}`, reason?.stack || '');
|
||||
const now = Date.now();
|
||||
if (now - unhandledRejectionReset > 60000) {
|
||||
unhandledRejectionCount = 0;
|
||||
unhandledRejectionReset = now;
|
||||
}
|
||||
unhandledRejectionCount++;
|
||||
if (unhandledRejectionCount >= 5) {
|
||||
log('ERROR', `Too many unhandled rejections (${unhandledRejectionCount}/min) — aborting`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+12
-7
@@ -61,19 +61,24 @@ export function buildHealthPayload({
|
||||
|
||||
if (!includeMetrics) return payload;
|
||||
|
||||
const roomValues = Array.from(rooms.values());
|
||||
const roomSizes = roomValues.map(room => room.peers?.size || 0);
|
||||
const peers = roomSizes.reduce((sum, size) => sum + size, 0);
|
||||
const maxPeersInRoom = roomSizes.length > 0 ? Math.max(...roomSizes) : 0;
|
||||
const avgPeersPerRoom = roomSizes.length > 0
|
||||
? Math.round((peers / roomSizes.length) * 100) / 100
|
||||
let peers = 0;
|
||||
let maxPeersInRoom = 0;
|
||||
let roomsWithLobby = 0;
|
||||
for (const room of rooms.values()) {
|
||||
const size = room.peers?.size || 0;
|
||||
peers += size;
|
||||
if (size > maxPeersInRoom) maxPeersInRoom = size;
|
||||
if (room.activeLobby) roomsWithLobby++;
|
||||
}
|
||||
const avgPeersPerRoom = rooms.size > 0
|
||||
? Math.round((peers / rooms.size) * 100) / 100
|
||||
: 0;
|
||||
const mem = memoryUsage();
|
||||
|
||||
return {
|
||||
...payload,
|
||||
peers,
|
||||
roomsWithLobby: roomValues.filter(room => !!room.activeLobby).length,
|
||||
roomsWithLobby,
|
||||
avgPeersPerRoom,
|
||||
maxPeersInRoom,
|
||||
rateLimits: {
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
# KoalaSync Translation & Localization Guide
|
||||
|
||||
Welcome to the **KoalaSync** translation and internationalization framework! This document provides clear, professional instructions for developers and contributors looking to maintain, audit, or add new languages to the official KoalaSync website.
|
||||
|
||||
---
|
||||
|
||||
## 🏛️ Architecture Overview
|
||||
|
||||
The KoalaSync website utilizes a custom, high-performance, zero-dependency static site generator built in Node.js. Instead of using complex client-side translation runtimes or bulky frameworks, localized pages are compiled ahead-of-time (AOT) to maintain lightning-fast page speeds and strict data sovereignty.
|
||||
|
||||
* **Template Source:** [`website/template.html`](file:///Users/koala/Documents/KoalaPlay/website/template.html) (Single Source of Truth)
|
||||
* **Locales Source:** `/website/locales/[lang].json` (Structured JSON translation dictionaries)
|
||||
* **Build Pipeline:** [`website/build.js`](file:///Users/koala/Documents/KoalaPlay/website/build.js) (Pure Node.js script that compiles pages into `/website/www/`)
|
||||
|
||||
---
|
||||
|
||||
## 📊 Supported Languages Dashboard
|
||||
|
||||
We divide supported languages into two tiers: **Core Languages** (fully hand-crafted and audited by native speakers) and **Extended Languages** (auto-generated using translation models to expand initial coverage).
|
||||
|
||||
> [!TIP]
|
||||
> **Help Us Improve!**
|
||||
> We welcome community contributions to audit "Auto-Generated" translations and elevate them to "Verified" status.
|
||||
|
||||
| Language Code | Language Name | Verification Status | Rationale / Context |
|
||||
| :--- | :--- | :--- | :--- |
|
||||
| `en` | 🇬🇧 **English** | `100% Manually Verified` | Primary developer language and system default |
|
||||
| `de` | 🇩🇪 **German** | `100% Manually Verified` | Core market and compliance baseline |
|
||||
| `fr` | 🇫🇷 **French** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `es` | 🇪🇸 **Spanish** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `pt-BR` | 🇧🇷 **Portuguese (Brasil)** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `ru` | 🇷🇺 **Russian** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `it` | 🇮🇹 **Italian** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `pl` | 🇵🇱 **Polish** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `tr` | 🇹🇷 **Turkish** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `nl` | 🇳🇱 **Dutch** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `ja` | 🇯🇵 **Japanese** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `ko` | 🇰🇷 **Korean** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
| `pt` | 🇵🇹 **European Portuguese** | `Auto-Generated` | Needs manual native review and polishing |
|
||||
|
||||
> [!WARNING]
|
||||
> **Autogeneration Quality Rule**
|
||||
> Any newly contributed languages must be committed as `"Auto-Generated"` until fully reviewed and signed off by a native speaker in a pull request.
|
||||
|
||||
---
|
||||
|
||||
## ⚖️ Strict Legal Exclusion Rule
|
||||
|
||||
Our legal pages have strict constraints to protect user privacy and avoid regulatory liabilities.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **DO NOT TRANSLATE LEGAL DOCUMENTS**
|
||||
> The legal notice ([impressum.html](file:///Users/koala/Documents/KoalaPlay/website/impressum.html)) and privacy policy ([datenschutz.html](file:///Users/koala/Documents/KoalaPlay/website/datenschutz.html)) **MUST remain exclusively in English and German**.
|
||||
>
|
||||
> * **Rationale:** Legal compliance under the European Union General Data Protection Regulation (GDPR) and the German Digital Services Act (DDG). Offering automated translations of legally binding notices introduces compliance risks due to potential mistranslations of liability limits.
|
||||
> * **Technical Fallback:** The dynamic initializer script (`lang-init.js`) is configured to automatically fallback to **English** for legal pages if a user visits them with a French, Spanish, or other unsupported language preference, keeping their dynamic dropdown choice intact for homepage links.
|
||||
|
||||
---
|
||||
|
||||
## 🛠️ Step-by-Step: Adding a New Language
|
||||
|
||||
Adding a new language (e.g., Italian - `it`) is straightforward. Follow these four structured steps:
|
||||
|
||||
### Step 1: Create the Translation Dictionary
|
||||
Create a new JSON file inside the locales directory named `[lang].json` (e.g., `website/locales/it.json`).
|
||||
1. Copy the structure of [`website/locales/en.json`](file:///Users/koala/Documents/KoalaPlay/website/locales/en.json) to use as your baseline.
|
||||
2. Translate all string values while keeping the JSON keys identical.
|
||||
3. Configure the language metadata keys at the top of the file:
|
||||
```json
|
||||
{
|
||||
"LANG_CODE": "it",
|
||||
"HTML_CLASS": "lang-it",
|
||||
"CANONICAL_PATH": "it/",
|
||||
"LANG_TOGGLE_URL": "../",
|
||||
"LANG_TOGGLE_TEXT": "EN"
|
||||
}
|
||||
```
|
||||
|
||||
### Step 2: Register in the Compiler
|
||||
Open the static site generator script [`website/build.js`](file:///Users/koala/Documents/KoalaPlay/website/build.js) and append your new language code to the active `languages` array:
|
||||
```javascript
|
||||
// Add 'it' to the array
|
||||
const languages = ['en', 'de', 'fr', 'es', 'pt-BR', 'ru', 'it'];
|
||||
```
|
||||
|
||||
### Step 3: Run the Build Script
|
||||
Execute the compiler from the root of the repository:
|
||||
```bash
|
||||
node website/build.js
|
||||
```
|
||||
The compiler will automatically:
|
||||
1. Load the new JSON translation file.
|
||||
2. Create the target subdirectory `/website/www/it/`.
|
||||
3. Generate the compiled `/website/www/it/index.html` landing page, injecting correct relative assets and canonical metadata.
|
||||
|
||||
### Step 4: Update the Dashboard
|
||||
Open this `TRANSLATION.md` file and add your language to the **Supported Languages Dashboard** table, marking it as `Auto-Generated` (unless manually verified).
|
||||
|
||||
---
|
||||
|
||||
## 🔮 Future Roadmap: Dynamic Utility Pages
|
||||
|
||||
For pages that require fully dynamic, client-side interactions (like the room invitation bridge [`join.html`](file:///Users/koala/Documents/KoalaPlay/website/join.html)), we need to scale to unlimited languages without bloating the HTML size or polluting the URL.
|
||||
|
||||
### Clean Client-Side i18n Architecture
|
||||
|
||||
To maintain zero URL pollution (e.g. keeping invitation links clean as `/join.html#join:roomID:password`), we propose an **asynchronous JSON dictionary injection architecture**:
|
||||
|
||||
#### 1. Page Lifecycle Flow
|
||||
1. **User Landing:** The guest enters `/join.html` with a shared hash.
|
||||
2. **Language Resolution:** `lang-init.js` immediately reads their saved preference (`localStorage` or `navigator.language`) and applies the active class (e.g. `html.lang = "es"`).
|
||||
3. **Async Fetching:** A client-side loader script (`i18n-client.js`) runs asynchronously, downloading the correct dictionary (`fetch("/locales/es.json")`).
|
||||
4. **DOM Translation:** The script scans the page for elements carrying a `data-i18n` attribute and safely updates their text content at runtime, avoiding dual-text nodes and stylesheet recalculations.
|
||||
|
||||
#### 2. Declarative HTML Markup
|
||||
Elements are defined with custom data attributes specifying translation keys. English text is placed as the static HTML fallback:
|
||||
```html
|
||||
<h1 data-i18n="JOIN_TITLE">Ready to sync?</h1>
|
||||
<p id="join-desc" data-i18n="JOIN_SUBTITLE">You've been invited to join a session.</p>
|
||||
```
|
||||
|
||||
#### 3. Zero-Dependency Engine (`i18n-client.js`)
|
||||
```javascript
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// 1. Recover the language determined during early initialization
|
||||
const activeLang = document.documentElement.lang || 'en';
|
||||
if (activeLang === 'en') return; // Default markup is already in English
|
||||
|
||||
// 2. Fetch the corresponding locale JSON asynchronously
|
||||
try {
|
||||
const response = await fetch(`locales/${activeLang}.json`);
|
||||
if (!response.ok) throw new Error('Locale file unavailable');
|
||||
const dictionary = await response.json();
|
||||
|
||||
// 3. Scan and translate data-i18n attributes
|
||||
document.querySelectorAll('[data-i18n]').forEach(el => {
|
||||
const key = el.getAttribute('data-i18n');
|
||||
if (dictionary[key]) {
|
||||
if (el.tagName === 'IMG') {
|
||||
el.alt = dictionary[key];
|
||||
} else {
|
||||
el.textContent = dictionary[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Dynamic i18n loading failed. Defaulting to English:', err);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
#### Core Benefits
|
||||
* **Zero URL Pollution:** Keeps invitation hashes private and avoids messy query parameters (`?lang=de`), protecting user privacy.
|
||||
* **Optimal Performance:** Eliminates duplicate hidden text blocks, cutting page weight in half and ensuring smooth rendering.
|
||||
* **Infinite Scale:** Adding new languages to dynamic pages requires zero edits to HTML markup; the engine simply fetches new JSON dictionaries on-demand.
|
||||
|
||||
---
|
||||
|
||||
## 🔌 Extension Internationalization (i18n)
|
||||
|
||||
In **v2.0**, we extended full internationalization support to the **Browser Extension itself**. The architecture mirrors our web-based dynamic localization model to maintain complete parity.
|
||||
|
||||
* **Locales Directory:** [`extension/locales/`](file:///Users/koala/Documents/KoalaPlay/extension/locales/)
|
||||
* **Active Dictionaries:**
|
||||
* [`en.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/en.json) (🇬🇧 Baseline English)
|
||||
* [`de.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/de.json) (🇩🇪 German)
|
||||
* [`fr.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/fr.json) (🇫🇷 French)
|
||||
* [`es.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/es.json) (🇪🇸 Spanish)
|
||||
* [`pt-BR.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/pt-BR.json) (🇧🇷 Portuguese (Brasil))
|
||||
* [`ru.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/ru.json) (🇷🇺 Russian)
|
||||
* [`it.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/it.json) (🇮🇹 Italian)
|
||||
* [`pl.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/pl.json) (🇵🇱 Polish)
|
||||
* [`tr.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/tr.json) (🇹🇷 Turkish)
|
||||
* [`nl.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/nl.json) (🇳🇱 Dutch)
|
||||
* [`ja.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/ja.json) (🇯🇵 Japanese)
|
||||
* [`ko.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/ko.json) (🇰🇷 Korean)
|
||||
* [`pt.json`](file:///Users/koala/Documents/KoalaPlay/extension/locales/pt.json) (🇵🇹 European Portuguese)
|
||||
* **Translation Engine:** [`extension/i18n.js`](file:///Users/koala/Documents/KoalaPlay/extension/i18n.js)
|
||||
* **Validation Script:** [`scripts/test-locales.js`](file:///Users/koala/Documents/KoalaPlay/scripts/test-locales.js)
|
||||
|
||||
### ⚙️ How it Works inside the Extension
|
||||
|
||||
1. **System Locale Auto-Detection**: On first run, the extension detects the browser system language using `navigator.language` or `chrome.i18n.getUILanguage()`.
|
||||
2. **On-the-Fly Redraws**: When the user selects a different language in the settings tab (`#langSelector`), the selection is stored in `chrome.storage.sync` and the translation engine immediately triggers `translateDOM()`. The interface, empty state cards, tooltips, dynamic onboarding tutorial guides, and status badges re-render instantly without reloading the popup.
|
||||
3. **Localized System Notifications**: On play, pause, or seek commands, `background.js` retrieves the user's active locale preference from storage, loads the correct dictionary, and pushes native OS notifications fully translated.
|
||||
|
||||
### 🧪 Auditing & Sync Checks
|
||||
|
||||
To ensure that no language dictionary falls out of sync (causing missing labels or blank interfaces), developers must run the locale auditor tool before packaging releases:
|
||||
```bash
|
||||
node scripts/test-locales.js
|
||||
```
|
||||
This script asserts that all JSON dictionary files under `extension/locales/` share exactly the same set of keys as the English baseline (`en.json`).
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
@@ -102,7 +102,7 @@
|
||||
"priceCurrency": "EUR"
|
||||
},
|
||||
"description": "Watch Netflix, YouTube, Twitch, Jellyfin, Emby and almost any HTML5 video in perfect sync with friends. Open source, privacy-first, free.",
|
||||
"softwareVersion": "2.2.4",
|
||||
"softwareVersion": "2.3.0",
|
||||
"license": "https://opensource.org/licenses/MIT",
|
||||
"sameAs": "https://github.com/Shik3i/KoalaSync",
|
||||
"image": "https://sync.koalastuff.net/assets/NewLogoIcon.webp",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"version": "2.2.4",
|
||||
"date": "2026-06-09T23:42:27Z"
|
||||
"version": "2.3.0",
|
||||
"date": "2026-06-14T03:17:45Z"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user