aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/i18n.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-08-11 04:13:53 +0200
committerChristophe Besson <cbesson@gmail.com>2026-08-11 04:13:53 +0200
commite23e33adeaf8ee7439187d4451c856b37816a51f (patch)
treea41eef1fba34cdd642d25576395b4ad484a748ad /packages/meshbay-hub/src/meshbay_hub/static/i18n.js
parent60c4570e72e36c2a9720593c8baec74ee2ab52d6 (diff)
downloadmeshbay-e23e33adeaf8ee7439187d4451c856b37816a51f.tar.gz
feat: Phase 9 — Web client SPA with WebRTC P2P transport
Complete browser-based client: Preact SPA with login, group file browser, encrypted download, video playback, group chat, i18n, and dark/light theme. Browser connects P2P to nodes behind residential NAT via WebRTC DataChannel (aiortc). Hub handles signaling only — all data flows E2E. Performance: pipelined downloads (8-chunk sliding window), binary msgpack wire format (no base64), redundant I/O elimination. Large file downloads stream to disk via File System Access API (showSaveFilePicker). Validated on SFR + Orange residential NATs, Chrome + Firefox, IPv4/IPv6. 132 tests passing. Deployed to meshbay.org + Orange node. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/i18n.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/i18n.js172
1 files changed, 172 insertions, 0 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/i18n.js b/packages/meshbay-hub/src/meshbay_hub/static/i18n.js
new file mode 100644
index 0000000..e0242cf
--- /dev/null
+++ b/packages/meshbay-hub/src/meshbay_hub/static/i18n.js
@@ -0,0 +1,172 @@
+/**
+ * MeshBay i18n — lightweight string localization.
+ *
+ * Usage:
+ * import { t, setLocale, getLocale, LOCALES } from './i18n.js';
+ * t('nav.logout') // "Logout"
+ * t('status.files', { n: 42 }) // "42 files"
+ */
+
+const LANG_KEY = 'mb_lang';
+
+// ── English strings ─────────────────────────────────────────────────────────
+
+const en = {
+ // Nav
+ 'nav.toggle_menu': 'Toggle menu',
+ 'nav.light_mode': 'Light mode',
+ 'nav.dark_mode': 'Dark mode',
+ 'nav.logout': 'Logout',
+ 'nav.login': 'Login',
+
+ // Sidebar
+ 'sidebar.my_groups': 'My Groups',
+ 'sidebar.no_groups': 'No groups yet',
+ 'sidebar.discover': 'Discover',
+ 'sidebar.public_groups': 'Public groups',
+
+ // Login
+ 'login.title': 'Login',
+ 'login.username': 'Username',
+ 'login.password': 'Password',
+ 'login.submit': 'Login',
+ 'login.loading': 'Logging in...',
+ 'login.no_account': 'No account?',
+ 'login.register_link': 'Register',
+
+ // Register
+ 'register.title': 'Register',
+ 'register.username': 'Username',
+ 'register.email': 'Email',
+ 'register.password': 'Password (min 8 chars)',
+ 'register.confirm': 'Confirm password',
+ 'register.submit': 'Register',
+ 'register.loading': 'Creating account...',
+ 'register.has_account': 'Already have an account?',
+ 'register.login_link': 'Login',
+ 'register.success_title': 'Account created',
+ 'register.success_msg': 'You can now log in with your credentials.',
+ 'register.go_login': 'Go to login',
+ 'register.err_mismatch': 'Passwords do not match',
+ 'register.err_min_len': 'Password must be at least 8 characters',
+
+ // Home
+ 'home.welcome': 'Welcome to MeshBay',
+ 'home.no_groups': 'You are not a member of any group yet.',
+ 'home.browse_prefix': 'Browse ',
+ 'home.browse_link': 'public groups',
+ 'home.browse_suffix': ' or ask a group admin to invite you.',
+ 'home.my_groups': 'My Groups',
+
+ // Explore
+ 'explore.title': 'Public Groups',
+ 'explore.loading': 'Loading...',
+ 'explore.empty': 'No public groups available.',
+
+ // Group page
+ 'group.default_name': 'Group',
+ 'group.tab_files': 'Files',
+ 'group.tab_chat': 'Chat',
+ 'group.filter': 'Filter files...',
+ 'group.col_name': 'Name',
+ 'group.col_size': 'Size',
+ 'group.col_type': 'Type',
+ 'group.col_date': 'Date',
+ 'group.empty_filter': 'No files match the filter',
+ 'group.empty_dir': 'This directory is empty',
+ 'group.download': 'Download',
+ 'group.play': 'Play',
+ 'group.dl_failed': 'Download failed: {err}',
+ 'group.offline_title': 'No nodes are currently online for this group.',
+ 'group.offline_hint': 'Files will appear when a node hosting this group connects.',
+ 'group.err_transport': 'Transport module not loaded',
+
+ // Status
+ 'status.idle': 'Idle',
+ 'status.discovering': 'Finding nodes...',
+ 'status.connecting': 'Connecting via WebRTC...',
+ 'status.fetching': 'Fetching index...',
+ 'status.files': '{n} files',
+ 'status.offline': 'No nodes online',
+ 'status.error': 'Connection failed',
+
+ // Chat
+ 'chat.empty': 'No messages yet. Start the conversation!',
+ 'chat.placeholder': 'Type a message...',
+ 'chat.send': 'Send',
+
+ // Video player
+ 'video.loading': 'Loading {name}...',
+ 'video.close': 'Close (Esc)',
+ 'video.err_transport': 'Transport not connected',
+
+ // Settings
+ 'settings.title': 'Settings',
+ 'settings.coming_soon': 'Coming soon.',
+ 'settings.profile': 'Profile',
+ 'settings.username': 'Username',
+ 'settings.appearance': 'Appearance',
+ 'settings.theme': 'Theme',
+ 'settings.theme_light': 'Light',
+ 'settings.theme_dark': 'Dark',
+ 'settings.theme_system': 'System',
+ 'settings.language': 'Language',
+ 'settings.about': 'About',
+ 'settings.version': 'Version',
+ 'settings.protocol': 'Protocol',
+
+ // Sidebar
+ 'sidebar.settings': 'Settings',
+};
+
+// ── Locale registry ─────────────────────────────────────────────────────────
+
+const _strings = { en };
+let _locale = 'en';
+
+export const LOCALES = [
+ { code: 'en', name: 'English' },
+];
+
+export function getLocale() { return _locale; }
+
+export function setLocale(code) {
+ if (_strings[code]) {
+ _locale = code;
+ localStorage.setItem(LANG_KEY, code);
+ return true;
+ }
+ return false;
+}
+
+export function addLocale(code, strings) {
+ _strings[code] = strings;
+}
+
+export function t(key, params) {
+ let s = (_strings[_locale] && _strings[_locale][key])
+ || _strings.en[key]
+ || key;
+ if (params) {
+ for (const [k, v] of Object.entries(params)) {
+ s = s.replace(`{${k}}`, v);
+ }
+ }
+ return s;
+}
+
+// ── Init ────────────────────────────────────────────────────────────────────
+
+function _init() {
+ const stored = localStorage.getItem(LANG_KEY);
+ if (stored && _strings[stored]) {
+ _locale = stored;
+ return;
+ }
+ const browserLang = (navigator.language || '').split('-')[0];
+ if (_strings[browserLang]) {
+ _locale = browserLang;
+ }
+}
+
+_init();