diff --git a/scripts/check-setup-needed.js b/scripts/check-setup-needed.js index 23272b52a..de5462d34 100644 --- a/scripts/check-setup-needed.js +++ b/scripts/check-setup-needed.js @@ -7,13 +7,12 @@ import fs from 'fs' import path from 'path' import dotenv from 'dotenv' import { execSync } from 'child_process' -import { L10N_CAGE_DIR, MARKDOWN_L10NS } from '../src/lib/l10n' -import { importRuntimeWithoutVite } from './l10n/utils' +import { L10N_CAGE_DIR, MARKDOWN_L10NS } from '../src/lib/l10n.js' dotenv.config() -const runtimeModule = await importRuntimeWithoutVite() -let activeLocales = Array.from(runtimeModule.locales) +import { locales } from '../src/lib/paraglide/runtime.js' +let activeLocales = Array.from(locales) let setupNeeded = false let reason = '' diff --git a/scripts/inlang-settings.ts b/scripts/inlang-settings.ts index 0ab569f51..eb3aeeb5b 100644 --- a/scripts/inlang-settings.ts +++ b/scripts/inlang-settings.ts @@ -124,9 +124,9 @@ function regenerateSettings(verbose = false): void { project: './project.inlang', outdir: './src/lib/paraglide', strategy: ['url', 'cookie', 'preferredLanguage', 'baseLocale'], - // Fix for Netlify Edge Functions (Deno runtime) - disableAsyncLocalStorage: true, - isServer: 'import.meta.env.SSR' + // Required for serverless environments like Netlify Edge Functions + // Safe because each request runs in isolated execution context + disableAsyncLocalStorage: true } // Only set urlPatterns for prefix-all-locales strategy diff --git a/scripts/l10n/run.ts b/scripts/l10n/run.ts index 0388a053d..53cdaadc2 100644 --- a/scripts/l10n/run.ts +++ b/scripts/l10n/run.ts @@ -40,7 +40,6 @@ import { MESSAGE_L10NS, MESSAGE_SOURCE } from '../../src/lib/l10n' -import { importRuntimeWithoutVite } from './utils' // Load environment variables first dotenv.config() @@ -71,21 +70,18 @@ if (unknownArgs.length > 0) { // Ensure inlang settings are current before importing runtime execSync('tsx scripts/inlang-settings.ts', { stdio: 'inherit' }) -// This let / try / catch lets the ESM scan succeed in the absence of a runtime -let locales: readonly string[] -try { - const runtime = await importRuntimeWithoutVite() - locales = runtime.locales - if (runtime.baseLocale !== 'en') - throw new Error( - `runtime.baseLocale set to ${runtime.baseLocale} but our code assumes and hardcodes 'en'` - ) -} catch (error: unknown) { - if (error instanceof Error) console.error('Failed to import runtime:', error.message) - else console.error('Failed to import runtime with unknown error:', error) - process.exit(1) +// Dynamic import after runtime is generated (ESM scan happens before execSync) +const runtime = await import('../../src/lib/paraglide/runtime.js') + +// Verify base locale assumption +if (runtime.baseLocale !== 'en') { + throw new Error( + `runtime.baseLocale set to ${runtime.baseLocale} but our code assumes and hardcodes 'en'` + ) } +const locales = runtime.locales + // Get API key early for mode determination const LLM_API_KEY = process.env.L10N_OPENROUTER_API_KEY diff --git a/scripts/l10n/utils.ts b/scripts/l10n/utils.ts index 7f37456c8..f58e7714b 100644 --- a/scripts/l10n/utils.ts +++ b/scripts/l10n/utils.ts @@ -249,14 +249,3 @@ export function cullCommentary(filePath: string, verbose = false) { ) } } - -export async function importRuntimeWithoutVite(): Promise< - typeof import('../../src/lib/paraglide/runtime.js') -> { - const runtimeString = await fs.readFile('src/lib/paraglide/runtime.js', 'utf-8') - const patchedRuntime = runtimeString.replace('import.meta.env.SSR', 'true') - const runtime = await import( - 'data:text/javascript;base64,' + Buffer.from(patchedRuntime).toString('base64') - ) - return runtime -} diff --git a/scripts/l10ntamer.test.ts b/scripts/l10ntamer.test.ts index af5b90c8b..afa0b425a 100644 --- a/scripts/l10ntamer.test.ts +++ b/scripts/l10ntamer.test.ts @@ -3,19 +3,13 @@ import fs from 'fs' import path from 'path' import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest' -// Mock the importRuntimeWithoutVite function from utils.ts -vi.mock('./l10n/utils.ts', async (importOriginal) => { - const actual = await importOriginal() - return { - ...actual, - importRuntimeWithoutVite: vi.fn().mockResolvedValue({ - localizeHref: vi - .fn() - .mockImplementation((href: string, { locale }: { locale: string }) => `/${locale}${href}`), - locales: ['en'] - }) - } -}) +// Mock the paraglide runtime module +vi.mock('../src/lib/paraglide/runtime.js', () => ({ + localizeHref: vi + .fn() + .mockImplementation((href: string, { locale }: { locale: string }) => `/${locale}${href}`), + locales: ['en'] +})) describe('findFilesRecursively', () => { const testDir = 'test_dir' diff --git a/scripts/l10ntamer.ts b/scripts/l10ntamer.ts index 690365817..fbfeedc16 100644 --- a/scripts/l10ntamer.ts +++ b/scripts/l10ntamer.ts @@ -11,9 +11,7 @@ import fs from 'fs' import path from 'path' -import { importRuntimeWithoutVite } from './l10n/utils.js' - -const { locales, localizeHref } = await importRuntimeWithoutVite() +import { locales, localizeHref } from '../src/lib/paraglide/runtime.js' if (localizeHref('/test', { locale: 'en' }) === '/test') { console.log('⏭️ Skipping l10ntamer - English routes not prefixed') diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 16d6ad298..b7c401f48 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,3 +1,20 @@ +// Fix for Netlify's Deno edge runtime (not standard Deno) +// +// Netlify's Deno 2.3.1 provides a partial window stub (window exists but window.location +// is undefined) for library compatibility. Standard Deno correctly has no window object. +// This partial stub breaks paraglide-js which assumes window.location exists if window exists. +// +// Deleting the stub makes Netlify's Deno behave like standard Deno/Node.js. +// See: notes/20251113-paraglide-edge-function-investigation.md +declare const Deno: unknown +if ( + typeof Deno !== 'undefined' && + typeof window !== 'undefined' && + typeof window.location === 'undefined' +) { + delete (globalThis as { window?: Window }).window +} + import { type Handle } from '@sveltejs/kit' import { paraglideMiddleware } from '$lib/paraglide/server.js' diff --git a/vite.config.ts b/vite.config.ts index f6cf153ae..0c9b89198 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,11 +5,9 @@ import fs from 'fs' import path from 'path' import { defineConfig } from 'vite' import lucidePreprocess from 'vite-plugin-lucide-preprocess' -import { importRuntimeWithoutVite } from './scripts/l10n/utils' import { isDev } from './src/lib/env' import { MARKDOWN_L10NS } from './src/lib/l10n' - -const { locales: compiledLocales } = await importRuntimeWithoutVite() +import { locales as compiledLocales } from './src/lib/paraglide/runtime.js' function getLocaleExcludePatterns(): RegExp[] { const md = path.resolve(MARKDOWN_L10NS)