velxio/frontend/src/main.tsx

68 lines
3.0 KiB
TypeScript
Raw Normal View History

import { createRoot } from 'react-dom/client';
import { loader } from '@monaco-editor/react';
import './index.css';
feat(i18n): react-i18next foundation + 9-locale support for header / footer This is Phase 1 of multi-language support: the visible chrome (header, footer, language switcher) and routing are wired up for all 9 locales (en, es, pt-br, it, fr, zh-cn, de, ja, ru) — same set the blog at velxio.dev/blog/ already supports. The Editor and the long-form landing-page copy are still English-only and will be translated in a follow-up. Infrastructure - frontend/src/i18n/config.ts: locale registry + LOCALE_META (htmlLang, native name, og locale, dir). Mirrors pro/blog/src/i18n/config.ts so cookie sync stays consistent. - frontend/src/i18n/cookie.ts: read/write the velxio_locale cookie at Path=/; Max-Age=1y; SameSite=Lax (Secure on HTTPS). The blog reads the same cookie via an inline script in its Layout.astro. - frontend/src/i18n/path.ts: getLocaleFromPath / stripLocaleFromPath / localizedPath / switchLocale / blogUrlFor — match the blog's helpers one-to-one. - frontend/src/i18n/index.ts: i18next bootstrap. English bundle is inlined synchronously for first paint; non-default locales are lazy-loaded via dynamic import on demand. Initial locale is decided in priority order URL > cookie > navigator > en. - frontend/src/i18n/LocaleSync.tsx: top-level wrapper inside <Router>. On every URL change loads the matching locale bundle, calls i18n.changeLanguage, writes the cookie, and mirrors the locale onto <html lang> and dir. - frontend/src/i18n/useLocalizedNavigate.ts: useCurrentLocale, useLocalizedHref, useLocalizedNavigate hooks for components that build internal links. Routing - App.tsx: route table extracted to a single ROUTES array, then registered twice — once at the root (default English) and once nested under each non-default locale (`/<locale>/...`). Explicit per-locale parent routes (rather than a generic `:lang` param) so React Router never accidentally swallows a real top-level path like `/circuit-simulator` as a locale segment. Header / Footer - LanguageSwitcher.tsx + .css: dropdown matching the blog's LanguageSwitcher.astro. Globe icon + locale code on the trigger, native names + ISO codes in the menu. Click → `switchLocale()` rewrites the URL under the new locale; LocaleSync handles the rest (load bundle, change language, write cookie). - AppHeader.tsx: every nav label and the auth dropdown copy now goes through `t('header.nav.*')`, `t('header.auth.*')`. All internal Links wrapped with localize() so navigation stays inside the active locale. Added a "Blog" link computed via `blogUrlFor(currentLocale)` so /es/ → /blog/es/, etc. - LandingPage.tsx: footer About-Velxio paragraph reads from t('footer.about'). Translations (Phase 1 strings) - frontend/src/i18n/locales/<locale>/common.json: nav labels, auth buttons, footer About copy. Hand-translated for all 9 locales, AGPLv3 / brand names preserved as-is. Tooling - frontend/scripts/translate-i18n.mjs: standalone Node script that takes the en.json bundles and auto-translates them to the 8 other locales via DeepSeek (primary) + Gemini (fallback). One LLM call per (locale, namespace) pair. Run after extracting new strings with `npm run translate:i18n`. Phase 2 (deferred) - Editor (toolbar, file explorer, simulator canvas, component picker, library manager, error toasts) — hundreds of strings. - Examples / Docs / About / Profile pages. - The translate-i18n.mjs script is ready to handle these once the strings have been extracted into JSON keys.
2026-05-09 10:40:37 +07:00
// Side-effect import: initialises i18next BEFORE any component renders so
// useTranslation() always resolves against a live instance. Must come
// before App.
import './i18n';
import './components/velxio-components/IC74HC595';
import './components/velxio-components/LogicGateElements';
import './components/velxio-components/TransistorElements';
import './components/velxio-components/OpAmpElements';
import './components/velxio-components/PowerElements';
import './components/velxio-components/DiodeElements';
import './components/velxio-components/RelayElements';
import './components/velxio-components/LogicICElements';
import './components/velxio-components/FlipFlopElements';
import './components/velxio-components/RaspberryPi3Element';
import './components/velxio-components/Bmp280Element';
refactor: rename wokwi-libs/ → third-party/ The directory grew well beyond Wokwi-only contents: it now hosts lcgamboa's QEMU fork (qemu-lcgamboa), Espressif's esp32-camera, the ngspice WASM build, fritzing-parts, picowi, an alternative QEMU (qemu-esp32), the 100_Days_100_IoT_Projects examples repo, and Wokwi's own avr8js/rp2040js/wokwi-elements/wokwi-features/wokwi-boards. "wokwi-libs" was misleading — half the contents have nothing to do with Wokwi. "third-party/" is the standard convention for vendored external dependencies. Mechanical changes: Path rename: wokwi-libs/ → third-party/ update-wokwi-libs.bat → update-third-party.bat docs/WOKWI_LIBS.md → docs/THIRD_PARTY.md Submodule reconfiguration: .gitmodules — 4 path= and section names updated .git/modules/wokwi-libs/ → .git/modules/third-party/ each submodule's .git file rewired to ../../.git/modules/third-party/<name> Reference updates (~80 files): vite.config.ts aliases, Dockerfile COPY paths, GH Actions workflow steps, build_qemu_*.sh, all docs/* and test/*/autosearch/* entries that mention the path, package-lock.json file: dependencies, .gitignore patterns, sitemap.xml + index.html SEO blurbs, scripts/generate-component-*, .dockerignore, .idea/vcs.xml. Bulk replaced both `wokwi-libs/` (path) and bare `wokwi-libs` (textual mentions in docs/comments). Verified: - npx tsc -b --noEmit produces no new errors related to these paths - vite.config.ts aliases now point at ../third-party/avr8js etc. - All 4 git submodules (avr8js, rp2040js, wokwi-elements, wokwi-features) are linked under third-party/ with their worktrees re-populated and config files referencing the new path - `grep -r wokwi-libs` returns zero hits outside node_modules, .vite, frontend/dist, third-party/ (upstream submodule contents), *.pyc caches, and *.dll.pre-camera rollback binaries Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-03 10:58:57 +07:00
import './components/velxio-components/EPaperElement';
import App from './App.tsx';
// Configure monaco-editor for offline use via local static assets
const monacoVsPath = `${import.meta.env.BASE_URL}monaco/vs`;
loader.config({ paths: { vs: monacoVsPath } });
createRoot(document.getElementById('root')!).render(<App />);
feat(desktop): hide header strip, splash screen, native locale switcher Three QoL fixes for the Tauri shell: 1. Hide the entire AppHeader strip in VITE_DESKTOP, not just the marketing nav. The previous gate left the black bar painting over the editor with the brand + auto-save + share + auth slot, all of which are irrelevant in desktop (cloud Pro features, license is handled by DesktopWelcomePage, the title bar already says "Velxio Desktop"). Return null at the top so the editor takes the full window height. 2. Splash screen during sidecar boot + Monaco hydration. Cold launch was a 3-8 s black window — now there's an inline SVG logo, "Velxio" wordmark, slogan, animated spinner, and a "Starting local backend…" caption. Lives in index.html as a fixed-position overlay with display:none by default; the inline script reveals it only when `window.__TAURI__` is present, so web users never see it. main.tsx fades it out (250 ms ease-out) after two animation frames — guarantees React's first paint has committed before the handoff, no black flash. Self-contained: inline styles, inline SVG, inline CSS keyframes, zero external requests. 3. Native locale switcher under View → Language. Emits `velxio://menu` with action='set-locale' + the locale code; the desktop/menu.ts handler navigates via history.pushState + popstate so React Router picks it up without a hard reload (Monaco + simulator state preserved). Locale list mirrors i18n/config.ts::LOCALES. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-23 05:39:10 +07:00
// Tear down the Tauri-only splash now that React has mounted. Wait
// two animation frames so React's first paint commits before we
// touch the splash — otherwise users see a black flash between the
// splash fading and the editor first appearing. Fade via CSS
// transition for a smoother handoff, then remove the node entirely
// once the transition finishes.
requestAnimationFrame(() => {
requestAnimationFrame(() => {
const splash = document.getElementById('velxio-splash');
if (!splash) return;
splash.style.transition = 'opacity 250ms ease-out';
splash.style.opacity = '0';
splash.style.pointerEvents = 'none';
window.setTimeout(() => splash.remove(), 320);
});
});
// Optional pro overlay. The `@pro` import resolves to a no-op stub in the
// open-source build (see vite.config.ts) and to the real overlay only when
// VITE_PRO_BUILD=true at build time. The dynamic import keeps the pro chunk
// out of the OSS bundle entirely (Vite tree-shakes the never-taken branch).
//
// VITE_DESKTOP=true is set by the Tauri desktop build. The desktop shell
// owns its own license + auth UI (Phase 3 of paid-clients) and runs against
// a locally spawned sidecar, so the velxio.dev-coupled overlay (trackers,
// billing, cloud auth, admin) is intentionally NOT loaded — even if a
// build accidentally sets both flags.
if (import.meta.env.VITE_PRO_BUILD && !import.meta.env.VITE_DESKTOP) {
import('@pro/index')
.then((m) => m.mountPro?.())
.catch((err) => console.warn('[pro] failed to load overlay:', err));
}
// Desktop-only hooks (ESP32 QEMU prompt now, welcome screen in Phase 3).
// Dynamic import so the OSS bundle never pulls this in.
if (import.meta.env.VITE_DESKTOP) {
import('./desktop/index')
.then((m) => m.mountDesktop?.())
.catch((err) => console.warn('[desktop] failed to load hooks:', err));
}