diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx index 0a1f924..c446eb3 100644 --- a/app/[lang]/page.tsx +++ b/app/[lang]/page.tsx @@ -1,4 +1,5 @@ "use client" +import { usePathname, useRouter } from "next/navigation" import { useCallback, useEffect, useRef, useState } from "react" import { DrawIoEmbed } from "react-drawio" import type { ImperativePanelHandle } from "react-resizable-panels" @@ -10,6 +11,7 @@ import { ResizablePanelGroup, } from "@/components/ui/resizable" import { useDiagram } from "@/contexts/diagram-context" +import { i18n, type Locale } from "@/lib/i18n/config" const drawioBaseUrl = process.env.NEXT_PUBLIC_DRAWIO_BASE_URL || "https://embed.diagrams.net" @@ -24,6 +26,8 @@ export default function Home() { showSaveDialog, setShowSaveDialog, } = useDiagram() + const router = useRouter() + const pathname = usePathname() const [isMobile, setIsMobile] = useState(false) const [isChatVisible, setIsChatVisible] = useState(true) const [drawioUi, setDrawioUi] = useState<"min" | "sketch">("min") @@ -58,6 +62,18 @@ export default function Home() { // Load preferences from localStorage after mount useEffect(() => { + // Restore saved locale and redirect if needed + const savedLocale = localStorage.getItem("next-ai-draw-io-locale") + if (savedLocale && i18n.locales.includes(savedLocale as Locale)) { + const pathParts = pathname.split("/").filter(Boolean) + const currentLocale = pathParts[0] + if (currentLocale !== savedLocale) { + pathParts[0] = savedLocale + router.replace(`/${pathParts.join("/")}`) + return // Wait for redirect + } + } + const savedUi = localStorage.getItem("drawio-theme") if (savedUi === "min" || savedUi === "sketch") { setDrawioUi(savedUi) @@ -84,7 +100,7 @@ export default function Home() { } setIsLoaded(true) - }, []) + }, [pathname, router]) const handleDarkModeChange = async () => { await saveDiagramToStorage() diff --git a/components/settings-dialog.tsx b/components/settings-dialog.tsx index 5ef9135..513ad05 100644 --- a/components/settings-dialog.tsx +++ b/components/settings-dialog.tsx @@ -151,6 +151,9 @@ function SettingsContent({ }, [open]) const changeLanguage = (lang: string) => { + // Save locale to localStorage for persistence across restarts + localStorage.setItem("next-ai-draw-io-locale", lang) + const parts = pathname.split("/") if (parts.length > 1 && i18n.locales.includes(parts[1] as Locale)) { parts[1] = lang diff --git a/electron/main/port-manager.ts b/electron/main/port-manager.ts index eb45379..2107ed7 100644 --- a/electron/main/port-manager.ts +++ b/electron/main/port-manager.ts @@ -3,16 +3,16 @@ import { app } from "electron" /** * Port configuration + * Using fixed ports to preserve localStorage across restarts + * (localStorage is origin-specific, so changing ports loses all saved data) */ const PORT_CONFIG = { // Development mode uses fixed port for hot reload compatibility development: 6002, - // Production mode port range (will find first available) - production: { - min: 10000, - max: 65535, - }, - // Maximum attempts to find an available port + // Production mode uses fixed port (61337) to preserve localStorage + // Falls back to sequential ports if unavailable + production: 61337, + // Maximum attempts to find an available port (fallback) maxAttempts: 100, } @@ -36,19 +36,11 @@ export function isPortAvailable(port: number): Promise { }) } -/** - * Generate a random port within the production range - */ -function getRandomPort(): number { - const { min, max } = PORT_CONFIG.production - return Math.floor(Math.random() * (max - min + 1)) + min -} - /** * Find an available port * - In development: uses fixed port (6002) - * - In production: finds a random available port - * - If a port was previously allocated, verifies it's still available + * - In production: uses fixed port (61337) to preserve localStorage + * - Falls back to sequential ports if preferred port is unavailable * * @param reuseExisting If true, try to reuse the previously allocated port * @returns Promise The available port @@ -56,6 +48,9 @@ function getRandomPort(): number { */ export async function findAvailablePort(reuseExisting = true): Promise { const isDev = !app.isPackaged + const preferredPort = isDev + ? PORT_CONFIG.development + : PORT_CONFIG.production // Try to reuse cached port if requested and available if (reuseExisting && allocatedPort !== null) { @@ -69,29 +64,22 @@ export async function findAvailablePort(reuseExisting = true): Promise { allocatedPort = null } - if (isDev) { - // Development mode: use fixed port - const port = PORT_CONFIG.development - const available = await isPortAvailable(port) - if (available) { - allocatedPort = port - return port - } - console.warn( - `Development port ${port} is in use, finding alternative...`, - ) + // Try preferred port first + if (await isPortAvailable(preferredPort)) { + allocatedPort = preferredPort + return preferredPort } - // Production mode or dev port unavailable: find random available port - for (let attempt = 0; attempt < PORT_CONFIG.maxAttempts; attempt++) { - const port = isDev - ? PORT_CONFIG.development + attempt + 1 - : getRandomPort() + console.warn( + `Preferred port ${preferredPort} is in use, finding alternative...`, + ) - const available = await isPortAvailable(port) - if (available) { + // Fallback: try sequential ports starting from preferred + 1 + for (let attempt = 1; attempt <= PORT_CONFIG.maxAttempts; attempt++) { + const port = preferredPort + attempt + if (await isPortAvailable(port)) { allocatedPort = port - console.log(`Allocated port: ${port}`) + console.log(`Allocated fallback port: ${port}`) return port } }