diff --git a/frontend/src/App.css b/frontend/src/App.css
index 7812bbfe..fd549460 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -633,7 +633,19 @@ body {
background: #171a21;
}
-.explorer-account-footer:has([data-auth-user]) .corner-lang,
-.editor-corner-box:has([data-auth-user]) .corner-lang {
+.explorer-account-footer {
+ container-type: inline-size;
+}
+
+/* Tight footer (narrow explorer): the avatar alone identifies the account;
+ the name is a luxury that costs exactly the width we do not have. */
+@container (max-width: 150px) {
+ .explorer-account-footer .header-username-text {
+ display: none;
+ }
+}
+
+/* Collapsed explorer: the floating fallback box shows the circle only. */
+.editor-corner-box .header-username-text {
display: none;
}
diff --git a/frontend/src/components/layout/LanguageSwitcher.css b/frontend/src/components/layout/LanguageSwitcher.css
index 4fc3cc7e..e7d28cd5 100644
--- a/frontend/src/components/layout/LanguageSwitcher.css
+++ b/frontend/src/components/layout/LanguageSwitcher.css
@@ -76,18 +76,4 @@
letter-spacing: 0.4px;
}
-/* Inside the editor's bottom-left corner box the switcher sits at the
- bottom edge of the screen — the menu must drop UP or it opens straight
- into the void below the viewport. */
-.editor-corner-box .velxio-language-menu {
- top: auto;
- bottom: 110%;
- margin: 0 0 6px;
-}
-/* Same drop-up need at the explorer footer. */
-.explorer-account-footer .velxio-language-menu {
- top: auto;
- bottom: 110%;
- margin: 0 0 6px;
-}
diff --git a/frontend/src/i18n/LocaleSync.tsx b/frontend/src/i18n/LocaleSync.tsx
index 296f7cd8..a714e3f1 100644
--- a/frontend/src/i18n/LocaleSync.tsx
+++ b/frontend/src/i18n/LocaleSync.tsx
@@ -11,18 +11,35 @@
*/
import { useEffect, type ReactNode } from "react";
-import { useLocation } from "react-router-dom";
+import { useLocation, useNavigate } from "react-router-dom";
import { i18n, loadLocale } from "./index";
-import { LOCALE_META, DEFAULT_LOCALE } from "./config";
-import { getLocaleFromPath } from "./path";
+import { LOCALE_META, DEFAULT_LOCALE, type Locale } from "./config";
+import { getLocaleFromPath, switchLocale } from "./path";
import { writeLocaleCookie } from "./cookie";
type Props = { children: ReactNode };
export function LocaleSync({ children }: Props) {
const { pathname } = useLocation();
+ const navigate = useNavigate();
const target = getLocaleFromPath(pathname);
+ // Locale switching for UI that lives OUTSIDE the Router: the pro account
+ // menu is injected into its own React root, so it cannot call
+ // useNavigate() — its language rows dispatch this event instead, and the
+ // switch happens here as a normal SPA navigation (no reload, workspace
+ // intact). Same path the header globe takes.
+ useEffect(() => {
+ const onSwitch = (e: Event): void => {
+ const locale = (e as CustomEvent<{ locale?: string }>).detail?.locale;
+ if (!locale) return;
+ const next = switchLocale(window.location.pathname, locale as Locale);
+ navigate(next + window.location.search + window.location.hash);
+ };
+ window.addEventListener('velxio-locale-switch', onSwitch);
+ return () => window.removeEventListener('velxio-locale-switch', onSwitch);
+ }, [navigate]);
+
useEffect(() => {
let cancelled = false;
(async () => {
diff --git a/frontend/src/pages/EditorPage.tsx b/frontend/src/pages/EditorPage.tsx
index e9ebdf96..468fd250 100644
--- a/frontend/src/pages/EditorPage.tsx
+++ b/frontend/src/pages/EditorPage.tsx
@@ -29,7 +29,6 @@ import { showConfirmDialog } from '../store/useMessageDialogStore';
import { useAutoSaveProject } from '../hooks/useAutoSaveProject';
import { registerEditorCommand } from '../lib/editorCommands';
import { EditorMenuBar } from '../components/editor/EditorMenuBar';
-import { LanguageSwitcher } from '../components/layout/LanguageSwitcher';
import type { CompilationLog } from '../utils/compilationLogger';
import '../App.css';
@@ -390,20 +389,15 @@ export const EditorPage: React.FC = () => {
there used to be 44+38. On narrow widths the strip wraps internally
and the header grows; the docked AI chat is avoided by the same
padding-right the strip always had. */
- /* Account + language block. Fused as the explorer panel's footer so a
- long file tree scrolls ABOVE it instead of disappearing underneath a
- floating box; when the explorer is collapsed it falls back to the
- small fixed corner box. The standalone language switcher only shows
- while logged OUT — signed-in users change language inside the account
- menu (see pro HeaderAuth), and a CSS :has() rule hides the globe when
- the auth slot renders a logged-in marker. */
+ /* Account block. Fused as the explorer panel's footer so a long file
+ tree scrolls ABOVE it instead of disappearing underneath a floating
+ box; when the explorer is collapsed it falls back to the small fixed
+ corner box (avatar only there — no room for a name). */
const accountBlock = !isMobile ? (
- <>
-
-